Pràctica 2 - Neteja i anàlisi de les dades
Autors: Carlos Molina i Carlos Gómez
# Importem llibreries
import os
import pandas as pd
import seaborn
import matplotlib.pyplot as plt
import sqlite3
import seaborn as sns
import scipy.stats as stats
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from statsmodels.stats.multicomp import pairwise_tukeyhsd, MultiComparison
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.manifold import TSNE
import plotly.graph_objects as go
import plotly.offline as pyo
pyo.init_notebook_mode()
El dataset triat per a aquesta pràctica es diu: European Soccer Data i es pot trobar en aquest link. En aquesta pràctica el trobarem dins del directori .\data\soccer_database.sqlite i l'haurem d'haver descarregat previament.
# Agafem el dataset del nostre repository
folder = os.path.join(os.getcwd(),'data')
Ens trobem davant un dataset amb dades detallades sobre més de 25.000 partits de futbol a les lligues professionals d'Europa. Aquest dataset inclou informació sobre els equips de cada país, els seus jugadors, dades de les temporades 2008 a 2016 i informació detallada de cada partit (marcador, possesió, faltes, corners, targetes, ...). Tot això ho tenim recollit en les següents taules:
Comentar que les dades dels atributs que tenim sobre els equips i els jugadors estan extrets del videojoc FIFA (http://sofifa.com), per complementar la informació original.
També es pot arrivar a visualitzar la posició al camp dels jugadors gràcies als atributs amb les coordenades X i Y, per si volguessim representar gràficament les posicions.
Gràcies a la gran quantitat d'informació que tenim amb aquest dataset podem arribar a fer molts tipus d'analisis diferents. Els que hem decidit fer són:
En aquest apartat realitzarem la preparació de les dades, seleccionant les dades que necessitarem del dataset original per realitzar els anàlisis posteriors i construcció de models predictius.
# Carreguem les dades en dataframes separats per la seva posterior utilització
conn = sqlite3.connect(os.path.join(folder,'soccer_database.sqlite'))
query_country = 'SELECT id, name FROM Country'
query_league = 'SELECT id, country_id, name FROM League'
query_match = 'SELECT id, country_id, league_id, season, stage, date, match_api_id, home_team_api_id, away_team_api_id, home_team_goal, away_team_goal, home_player_X1, home_player_X2, home_player_X3, home_player_X4, home_player_X5, home_player_X6, home_player_X7, home_player_X8, home_player_X9, home_player_X10, home_player_X11, away_player_X1, away_player_X2, away_player_X3, away_player_X4, away_player_X5, away_player_X6, away_player_X7, away_player_X8, away_player_X9, away_player_X10, away_player_X11, home_player_Y1, home_player_Y2, home_player_Y3, home_player_Y4, home_player_Y5, home_player_Y6, home_player_Y7, home_player_Y8, home_player_Y9, home_player_Y10, home_player_Y11, away_player_Y1, away_player_Y2, away_player_Y3, away_player_Y4, away_player_Y5, away_player_Y6, away_player_Y7, away_player_Y8, away_player_Y9, away_player_Y10, away_player_Y11, home_player_1, home_player_2, home_player_3, home_player_4, home_player_5, home_player_6, home_player_7, home_player_8, home_player_9, home_player_10, home_player_11, away_player_1, away_player_2, away_player_3, away_player_4, away_player_5, away_player_6, away_player_7, away_player_8, away_player_9, away_player_10, away_player_11, goal, shoton, shotoff, foulcommit, card, cross, corner, possession, B365H, B365D, B365A, BWH, BWD, BWA, IWH, IWD, IWA, LBH, LBD, LBA, PSH, PSD, PSA, WHH, WHD, WHA, SJH, SJD, SJA, VCH, VCD, VCA, GBH, GBD, GBA, BSH, BSD, BSA FROM Match'
query_player = 'SELECT id, player_api_id, player_name, player_fifa_api_id, birthday, height, weight FROM Player'
query_player_attributes = 'SELECT id, player_fifa_api_id, player_api_id, date, overall_rating, potential, preferred_foot, attacking_work_rate, defensive_work_rate, crossing, finishing, heading_accuracy, short_passing, volleys, dribbling, curve, free_kick_accuracy, long_passing, ball_control, acceleration, sprint_speed, agility, reactions, balance, shot_power, jumping, stamina, strength, long_shots, aggression, interceptions, positioning, vision, penalties, marking, standing_tackle, sliding_tackle, gk_diving, gk_handling, gk_kicking, gk_positioning, gk_reflexes FROM Player_Attributes'
query_team = 'SELECT id, team_api_id, team_fifa_api_id, team_long_name, team_short_name FROM Team'
query_team_attributes = 'SELECT id, team_fifa_api_id, team_api_id, date, buildUpPlaySpeed, buildUpPlaySpeedClass, buildUpPlayDribbling, buildUpPlayDribblingClass, buildUpPlayPassing, buildUpPlayPassingClass, buildUpPlayPositioningClass, chanceCreationPassing, chanceCreationPassingClass, chanceCreationCrossing, chanceCreationCrossingClass, chanceCreationShooting, chanceCreationShootingClass, chanceCreationPositioningClass, defencePressure, defencePressureClass, defenceAggression, defenceAggressionClass, defenceTeamWidth, defenceTeamWidthClass, defenceDefenderLineClass FROM Team_Attributes'
df_country = pd.read_sql_query(query_country, conn)
df_league = pd.read_sql_query(query_league, conn)
df_match = pd.read_sql_query(query_match, conn)
df_player = pd.read_sql_query(query_player, conn)
df_player_attributes = pd.read_sql_query(query_player_attributes, conn)
df_team = pd.read_sql_query(query_team, conn)
df_team_attributes = pd.read_sql_query(query_team_attributes, conn)
conn.close()
Aquests Dataframes els utilitzarem com a base per realitzar els anàlisis, les tasques de neteja, revisió de valors buits i identificació de valors extrems.
Una de les preguntes que volem respondre és quina diferència hi ha entre els equips de cada part quartil de la classificació per a diferents lligues i al final de cada temporada.
Per a fer això, extraurem els resultat de cada partit del Dataframe df_match, assignarem punts a cada equip, i agregarem els punts de cada equip per a cada temporada.
A més, en cas d'empat de punts, és donarà una posició més alta a aquells equips amb major diferència de gols i, en cas d'haver-hi la mateixa diferència de gols, a l'equip amb més gols a favor.
df_match_to_classification = df_match[['league_id','season', 'stage',
'home_team_api_id','away_team_api_id',
'home_team_goal','away_team_goal']].copy()
df_match_to_classification['home_team'] = df_match_to_classification['home_team_api_id'].replace(
df_team[['team_api_id','team_long_name']].set_index('team_api_id').to_dict()['team_long_name']
)
df_match_to_classification['away_team'] = df_match_to_classification['away_team_api_id'].replace(
df_team[['team_api_id','team_long_name']].set_index('team_api_id').to_dict()['team_long_name']
)
df_match_to_classification['league_name'] = df_match_to_classification['league_id'].replace(
df_league[['id','name']].set_index('id').to_dict()['name']
)
df_match_to_classification['away_team_goal_against'] = df_match_to_classification['home_team_goal']
df_match_to_classification['home_team_goal_against'] = df_match_to_classification['away_team_goal']
df_match_to_classification = df_match_to_classification[['season', 'league_name', 'home_team', 'away_team',
'home_team_goal', 'away_team_goal', 'away_team_goal_against',
'home_team_goal_against']]
df_match_to_classification.loc[df_match_to_classification['home_team_goal'] > df_match_to_classification['away_team_goal'], 'home_points'] = 3
df_match_to_classification.loc[df_match_to_classification['home_team_goal'] > df_match_to_classification['away_team_goal'], 'away_points'] = 0
df_match_to_classification.loc[df_match_to_classification['home_team_goal'] == df_match_to_classification['away_team_goal'], 'home_points'] = 1
df_match_to_classification.loc[df_match_to_classification['home_team_goal'] == df_match_to_classification['away_team_goal'], 'away_points'] = 1
df_match_to_classification.loc[df_match_to_classification['home_team_goal'] < df_match_to_classification['away_team_goal'], 'home_points'] = 0
df_match_to_classification.loc[df_match_to_classification['home_team_goal'] < df_match_to_classification['away_team_goal'], 'away_points'] = 3
df_classification = df_match_to_classification[['season','league_name','home_team','home_team_goal','home_team_goal_against','home_points']].rename(
columns={
'home_team':'team',
'home_team_goal':'goals',
'home_team_goal_against':'goals_against',
'home_points':'points'
}
).append(
df_match_to_classification[['season','league_name','away_team','away_team_goal','away_team_goal_against','away_points']].rename(
columns={
'away_team':'team',
'away_team_goal':'goals',
'away_team_goal_against':'goals_against',
'away_points':'points'
}
)
)
df_classification['matches'] = 1
df_classification = df_classification.groupby(['season','league_name','team']).sum().reset_index()
df_classification['dif_goals'] = df_classification['goals'] - df_classification['goals_against']
df_classification = df_classification.sort_values(['season','league_name','points','dif_goals','goals'],
ascending=[True,True, False,False,False])
df_classification['pos'] = 1
df_classification['pos'] = df_classification.groupby(['season', 'league_name'])['pos'].cumsum()
df_classification.head()
També volem comparar els ratings dels equips basat en la puntuacuó mitjana de cada jugador per a cada una de les temporades analitzades. El repte és que cada jugador pot tenir més d'una puntuació per a una mateixa temporada. Per tant, haurem de seguir el següent procés:
# Comprovem les dades màximes i mínimes de cada temporada
df_match.groupby('season').agg({'date':['min','max']})
Podem assegurar que la temporada a totes les lligues està compresa entre l'1 de Juliol i el 30 de Juny. seguidament, mapegem les temporades a la taula dels ratings dels jugadors, i calculem la mitjana de cada jugador.
df_ratings = df_player_attributes[['player_api_id', 'date', 'overall_rating']].copy()
df_ratings['date'] = pd.to_datetime(df_ratings['date'])
idx_beggining = df_ratings[df_ratings['date'].dt.month >= 7].index
idx_end = df_ratings[df_ratings['date'].dt.month < 7].index
df_ratings.loc[idx_beggining, 'season'] = df_ratings.loc[idx_beggining, 'date'].dt.year.astype(str) + '/' + (df_ratings.loc[idx_beggining, 'date'].dt.year + 1).astype(str)
df_ratings.loc[idx_end, 'season'] = (df_ratings.loc[idx_end, 'date'].dt.year - 1).astype(str) + '/' + df_ratings.loc[idx_end, 'date'].dt.year.astype(str)
df_ratings_season = df_ratings.groupby(['player_api_id','season'], as_index=False)[['overall_rating']].mean()
df_ratings_season.head()
Per a cada equip i temporada, comptabilitzem les aparicions de cada jugador.
df_match_players = df_match[['season', 'stage',
'home_team_api_id','away_team_api_id',
'home_player_1', 'home_player_2', 'home_player_3', 'home_player_4', 'home_player_5', 'home_player_6',
'home_player_7', 'home_player_8', 'home_player_9', 'home_player_10', 'home_player_11',
'away_player_1', 'away_player_2', 'away_player_3', 'away_player_4', 'away_player_5', 'away_player_6',
'away_player_7', 'away_player_8', 'away_player_9', 'away_player_10', 'away_player_11'
]].copy()
df_match_players['home_team'] = df_match_players['home_team_api_id'].replace(
df_team[['team_api_id','team_long_name']].set_index('team_api_id').to_dict()['team_long_name']
)
df_match_players['away_team'] = df_match_players['away_team_api_id'].replace(
df_team[['team_api_id','team_long_name']].set_index('team_api_id').to_dict()['team_long_name']
)
df_match_players = df_match_players[['season', 'stage','home_team'] + [x for x in df_match_players if 'home_player' in x]].rename(
columns = {
x:x[5:] for x in df_match_players.columns if 'home' in x
}
).append(
df_match_players[['season', 'stage','away_team'] + [x for x in df_match_players if 'away_player' in x]].rename(
columns = {
x:x[5:] for x in df_match_players.columns if 'away' in x
}
)
).set_index(['season','stage','team'])
df_match_unstacked = pd.DataFrame()
for col in df_match_players.columns:
df_match_unstacked = df_match_unstacked.append(df_match_players[[col]].rename(columns={col:'player_api_id'}))
df_match_unstacked = df_match_unstacked.reset_index()
df_match_unstacked_rating = df_match_unstacked.merge(df_ratings_season[['player_api_id','overall_rating','season']],
on=['player_api_id','season'],
how='left')
df_appear = df_match_unstacked_rating.groupby(['season','team','player_api_id'], as_index=False)['overall_rating'].count().rename(columns={'overall_rating':'appearences'})
df_player_rating_appearences = df_match_unstacked_rating.groupby(['season','team','player_api_id'], as_index=False)['overall_rating'].mean().merge(
df_appear, on=['season', 'team', 'player_api_id'], how='left')
df_player_rating_appearences.head()
Finalment, obtenim la puntuació per a cada equip i temporada.
df_player_rating_appearences = df_player_rating_appearences.merge(
df_player_rating_appearences.groupby(['season','team'], as_index=False)['appearences'].sum(),
on=['season','team'],
how='left',
suffixes=('','_total')
)
df_player_rating_appearences['weighed_rating'] = (
df_player_rating_appearences['overall_rating'] * df_player_rating_appearences['appearences'] /
df_player_rating_appearences['appearences_total']
)
df_season_team_rating = df_player_rating_appearences.groupby(
['season','team'],
as_index=False)[['weighed_rating']].sum()
df_season_team_rating.head()
Les dades utilitzades en aquesta pràctica són força netes. No obstant, hem observat que alguns del atributs dels jugadors contenen valors nuls.
df_player_attributes.info()
Tenim registres on tots els atributs son nuls (836 registres) i en altres casos es nomes en alguns atributs. És el cas, per exemple, dels atributs sliding_takle, agility, vision, jumping, balance, volleys, o agility. Això és degut a que el videojoc FIFA va incloure aquests atributs durant les temporades que s'abarquen en aquest estudi.
Una opció per a tracar la manca de dades seria no utilitzar aquestes variables. No obstant, degut a que no suposen un gran nombre i que aquestes són dades quantitatives, s'ha optat per utilitzar el mètode imperative imputer de la llibreria Sklearn de Python. Aquest mètode estima el valor de cada variable nul·la a partir de totes les altres variables, construint, en cada cas, un model de regresió lineal.
# inputem valors nuls
df_player_attributes_imputed = df_player_attributes.set_index('player_api_id').iloc[:,8:]
imp = IterativeImputer(max_iter=10, random_state=0)
df_player_attributes_imputed = pd.DataFrame(imp.fit_transform(df_player_attributes_imputed),
columns=df_player_attributes_imputed.columns,
index=df_player_attributes_imputed.index
)
# valors abans d'imputar
df_player_attributes.describe()['sliding_tackle']
# valors despres d'imputar
df_player_attributes_imputed.describe()['sliding_tackle']
Amb tots els atributs omplerts podem afegir les especificacions comuns amb les que es representen les caracteristiques dels jugadors al joc FIFA.
# Font: https://sofifa.com/player/176580/luis-suarez/160006/?attr=fut
df_player_attributes_imputed['pac'] = df_player_attributes_imputed[['acceleration','sprint_speed']].mean(axis=1)
df_player_attributes_imputed['sho'] = df_player_attributes_imputed[['positioning','finishing','shot_power','long_shots','volleys','penalties']].mean(axis=1)
df_player_attributes_imputed['pas'] = df_player_attributes_imputed[['vision','crossing','free_kick_accuracy','short_passing','long_passing','curve']].mean(axis=1)
df_player_attributes_imputed['dri'] = df_player_attributes_imputed[['agility','balance','reactions','ball_control','dribbling']].mean(axis=1)
df_player_attributes_imputed['def'] = df_player_attributes_imputed[['interceptions','heading_accuracy','marking','standing_tackle','sliding_tackle']].mean(axis=1)
df_player_attributes_imputed['phy'] = df_player_attributes_imputed[['jumping','stamina','strength','aggression']].mean(axis=1)
Un altre dels problemes identificats és que no es disposen de tots els partits per a totes les lligues i temporades. Per tant, s'han identificat en quins casos el nombre de jornades és menor a l'esperat i, en aquest cas, s'han elimitats aquelles temporades i lligues que no contenen un mínim de partits.
plt.boxplot(df_classification[['season','league_name','matches']].drop_duplicates()['matches']);
En el diagrame de caixa veiem que hi ha casos en el que el nombre de partits és menor de 30 o major de 40. En aquest cas, eliminarem de les dades aquestes lligues i temporades.
df_classification[['season','league_name','matches']].drop_duplicates()[
(df_classification[['season','league_name','matches']].drop_duplicates()['matches'] < 30)
|
(df_classification[['season','league_name','matches']].drop_duplicates()['matches'] > 40)
]
Veiem que la lliga suissa només va tenir 10 equips a la temporada 2011/2012 (https://en.wikipedia.org/wiki/2011%E2%80%9312_Swiss_Super_League), mentre que la lliga belga només en va tenir 15 a la temporada 2009/2010 (https://en.wikipedia.org/wiki/2009%E2%80%9310_Belgian_Pro_League), pel que considerarem aquests valors com a vàlids. La resta els considerarem outliers.
df_outliers = df_classification[['season','league_name','matches']].drop_duplicates()[
(df_classification[['season','league_name','matches']].drop_duplicates()['matches'] < 18)
|
(df_classification[['season','league_name','matches']].drop_duplicates()['matches'] > 40)
]
df_outliers['dummy'] = 0
classification_outliers_idx = df_classification.merge(
df_outliers.drop('matches', axis=1)
, on=['season','league_name'],
how='left'
).dropna().index
df_classification_clean = df_classification.loc[[x for x in df_classification.index if x not in classification_outliers_idx]]
df_classification_clean.head()
df_classification_grouped = df_classification_clean.groupby(
['season','league_name','team'],
as_index=False).sum()
df_classification_grouped['goals_diff'] = df_classification_grouped['goals'] - df_classification_grouped['goals_against']
df_classification_grouped = df_classification_grouped.sort_values(
['league_name', 'season','points','goals_diff','goals'],
ascending=[True, True, False, False, False]
).reset_index(drop=True)
df_classification_grouped = df_classification_grouped.merge(
df_classification_grouped.groupby(['season','league_name'])[['team']].count().rename(columns={'team':'tot_teams'}),
on=['season','league_name'],
how='left')
df_classification_grouped['position'] = 1
df_classification_grouped['position'] = df_classification_grouped.groupby(['season','league_name'])['position'].cumsum()
df_classification_grouped['quart'] = df_classification_grouped['position']/df_classification_grouped['tot_teams']
df_classification_grouped.loc[
(df_classification_grouped['quart'] <= .25), 'quartile'] = 1
df_classification_grouped.loc[(df_classification_grouped['quart'] > .25)
&
(df_classification_grouped['quart'] <= .5), 'quartile'] = 2
df_classification_grouped.loc[(df_classification_grouped['quart'] > .5)
&
(df_classification_grouped['quart'] <= .75), 'quartile'] = 3
df_classification_grouped.loc[(df_classification_grouped['quart'] > .75), 'quartile'] = 4
Afegim la qualitat de l'equip per a cada temporada basant-nos en la mitjana ponderada de la qualitat dels jugadors que van jugar els partits durant una temporada donada i que hem calculat anteriorment al punt 2.2.
df_classification_rating = df_classification_grouped.merge(df_season_team_rating,
on=['season','team'],
how='left')
df_classification_rating.head()
df_classification_rating['weighed_rating'].hist(bins=50)
from scipy import stats
_, p = stats.shapiro(df_classification_rating['weighed_rating'])
print(p)
Podem afirmar que la mostra és normal amb més d'un 99.9% de confiança, ja que per al test de Shapiro-Wilk obtenim una $p-valor<.001$
Repetim el mateix anàlisi per a totes les lliges:
for league in df_classification_rating['league_name'].unique():
df_temp_league = df_classification_rating[df_classification_rating['league_name']==league]
print(league, stats.shapiro(df_temp_league['weighed_rating'])[1])
Mirem també els sis atributs creats per als jugadors:
df_player_attributes_imputed
for qual in ['pac','sho','pas','dri','def','phy']:
plt.figure()
df_player_attributes_imputed[qual].hist(bins=30, label='Mean='+str("{:2.2f}".format(df_player_attributes_imputed[qual].mean())))
plt.axvline(df_player_attributes_imputed[qual].mean(), color='blue', linestyle='dashed', linewidth=1)
plt.title('{} - p-value={}'.format(qual, stats.shapiro(df_player_attributes_imputed[qual])[1]))
plt.legend(loc='upper right')
Observem com per la puntuació mitja ponderada dels equips tenim una distribució normal a les dades. Sobre el test de Shapiro-Wilk realitzat sobre les lligues, podem destacar que les distribucions menys normals son les que corresponen per aquest ordre, a la lliga Polaca, Belga i Sueca. Pel que fà als atributs dels jugadors, obtenim més freqüencia de casos per sobre de la mitjana en general. Destacar l'Histograma de l'atribut de la Defensa, on s'aprecia que els jugadors son generalment bons, o dolents defensivament.
Per a respondre aquesta pregunta, farem un contrast d'hipòtesis.
Escrivim les nostres hipòtesis:
$H_{0}$: Per a les quatre lligues amb més qualitat, la qualitat dels equips de cada quartil de la classificació al final de temporada és diferent.
$H_{1}$: No hi ha diferències en la qualitat dels equips entre algun dels quartils.
En primera instància, visualitzem la distribució de la qualitat dels equips al llarg de totes les temporades per a les diferents lligues. Veiem com les lligues anglesa, alemana, espanyola i italiana són les que tenen equips amb més qualitat i, per tant, són les lligues que compararem per a respondre a la pregunta:
ax, fig = plt.subplots(1,figsize=(10,6))
for league in df_classification_rating['league_name'].unique():
sns.distplot(df_classification_rating[df_classification_rating['league_name']==league]['weighed_rating'], hist=False, label=league)
Seleccionem les quatre lligues principals i fem l'anàlisi de Tukey, per a cada lliga, comparant els equips de diferents quantils. L'anàlisi de Tuckey realitza l'anàlisi de variància comparant tots els grups individualment. D'aquesta manera, es pot saber si dos grups en particular són iguals o diferents, independentment de la similaritat amb els altres grups.
df_class_rat_4 = df_classification_rating[df_classification_rating['league_name'].isin(
['England Premier League','Germany 1. Bundesliga', 'Italy Serie A','Spain LIGA BBVA']
)].copy()
for num, country in enumerate(df_class_rat_4['league_name'].unique()):
df_league = df_class_rat_4[df_class_rat_4['league_name'] == country]
print('\n'+country+'\n')
MultiComp = MultiComparison(df_league['weighed_rating'],
df_league['quartile'])
print(MultiComp.tukeyhsd().summary())
L'anàlisi de Tuckey mostra amb un 95% de confiança que només podem rebutjar la hipòtesi nul·la per entre equips del tercer i quart quartil de les lligues anglesa, alemana i espanyola; i entre equips del segon i tercer quartil de la lliga alemana.
Pel que fa la lliga italiana, els equips de tots els quartils són estadísticament diferents respecte als equips dels altres quartils de la classificació.
Si mirem la distància total entre el primer i quart quartil($meandiff$), observem que la lliga espanyola és la que mostra més diferència, seguida per la lliga italiana, anglesa i alemana. Per tant, podem afirmar que la lliga espanyola ha estat la més desigual intre els anys 2008 i 2016.
Seguint en la mateixa línea, volem saber si la puntuació dels equips respecte a la qualitat dels seus jugadors pot explicar la mitjana de punts per partit i, per tant, donar una idea de la posició en la classificació en la que es trobarà un equip a final de temporada. Per a estimar aquesta relació, construïm un model de regressió lineal.
# Calculem els punts per partit
df_classification_rating.loc[:,'points_per_match'] = df_classification_rating['points']/df_classification_rating['matches']
df_class_rat_4.loc[:,'points_per_match'] = df_class_rat_4['points']/df_class_rat_4['matches']
En primera instància, fem la regressió per a totes les lligues:
from scipy import stats
def mod(X,slope, intercept):
return X * slope + intercept
slope, intercept, r_value, p_value, std_err = stats.linregress(df_classification_rating['weighed_rating'],
df_classification_rating['points_per_match'])
print('Totes les lligues: \n R2 = {}'.format(
round(r_value**2,2)
))
El coeficient de determinació R$^{2}$, 0.24, indica una baixa correlació entre les dues variables. Aquest resultat probablement és degut a la desigualtat entre lligues a Europa. Per tant, un equip modest a una lliga secundària obtindrà un nombre alt de punts per partit; mentre que un equip amb la mateixa qualitat però que jugui a una gran lliga probablement obtindrà menys punts per partit.
Per aquest motiu, és raonable realitzar el mateix anàlisi per a cada lliga per separat:
for num, country in enumerate(df_class_rat_4['league_name'].unique()):
df_league = df_class_rat_4[df_class_rat_4['league_name'] == country]
slope, intercept, r_value, p_value, std_err = stats.linregress(df_league['weighed_rating'],
df_league['points_per_match'])
print('{}: \n R2 = {}'.format(
country,round(r_value**2,2)
))
Ara si, podem observar que degut a l'especifitat de cada lliga, la correlació entre qualitat de l'equip i punts per partits és més alta. En concret, aquesta relació és més alta en el cas de la lliga espanyola (R$^{2}$=0.78) la qual es pot explicar perquè durant moltes temporades dos equips en aquesta lliga amb una puntuació elevada van aconseguir una gran quantitat de punts. D'altra banda, en la lliga alemana és on auqesta relació és més feble (R$^{2}$=0.58). Aquests resultats coincideixen amb els obtinguts en l'apartat anterior.
Les diferències entre el primer quartil de la classificació i el tercer i quart quartil evidencia la diferència existent entre els jugadors dels equips de cada quartil. Obviament, els equips de la part baixa en la classificació no tenen suficient pressupost per a tenir jugadors com Messi, i s'han de conformar amb intentar trobar talent en jugadors joves que eventualment es converteixen en estrelles.
En aquest apartat volem saber si hi ha jugadors joves, de 21 anys o menys, que tinguin un perfil semblant a grans estrelles com Messi, Iniesta, o Piqué. D'aquesta manera, els equips més modestos es poden permetre jugadors de perfil similar als millors jugadors del món (tot i que amb menor puntuació).
S'han seguit els següents passos:
df_player_qual = df_player_attributes_imputed.iloc[:,:-6]
scaler = StandardScaler()
df_player_attributes_standard = pd.DataFrame(
scaler.fit_transform(
df_player_qual.T
), columns=df_player_qual.index
).T
#fem la mitjana dels valors estandarditzats dels jugadors
df_player_attributes_standard = df_player_attributes_standard.reset_index().groupby('player_api_id').mean()
kmeans = KMeans(n_clusters=10, random_state=0)
group = kmeans.fit_predict(df_player_attributes_standard)
df_results = pd.DataFrame(group,
index=df_player_attributes_standard.index,
columns=['cluster'])
df_results['cluster'] = df_results.astype(str)
df_results['cluster'] = 'cluster_' + df_results['cluster']
df_results.reset_index(inplace=True)
Un cop calculats els clústers, mirem a quin clúster es troben alguns jugadors. Jugadors que a l'inici de la temporada 2015/2016 tinguessin 21 anys o menys i tinguessin un perfil similar a determinats jugadors.
# Preparem les dades de jugadors joves a la temporada 2015/2016
young = df_player[pd.to_datetime(df_player['birthday']) >= pd.to_datetime('1994-07-01')]
df_young = df_results.merge(
young[['player_api_id','player_name']],
on='player_api_id',
how='inner'
).merge(
df_ratings_season[df_ratings_season['season']=='2015/2016'],
on='player_api_id',
how='inner'
)
df_young.head()
# Els millors 5 jugadors amb perfil similar a Messi
id_pl = df_player[df_player['player_name']=='Lionel Messi']['player_api_id'].values[0]
cluster = df_results[df_results['player_api_id'] == id_pl]['cluster'].values[0]
df_young[df_young['cluster'] == cluster].sort_values('overall_rating',
ascending=False)[['player_name','overall_rating']].head()
# Els millors 5 jugadors amb perfil similar a Andres Iniesta
id_pl = df_player[df_player['player_name']=='Andres Iniesta']['player_api_id'].values[0]
cluster = df_results[df_results['player_api_id'] == id_pl]['cluster'].values[0]
df_young[df_young['cluster'] == cluster].sort_values('overall_rating',
ascending=False)[['player_name','overall_rating']].head()
# Els millors 5 jugadors amb perfil similar a Gerard Pique
id_pl = df_player[df_player['player_name']=='Gerard Pique']['player_api_id'].values[0]
cluster = df_results[df_results['player_api_id'] == id_pl]['cluster'].values[0]
df_young[df_young['cluster'] == cluster].sort_values('overall_rating',
ascending=False)[['player_name','overall_rating']].head()
# Els millors 5 jugadors amb perfil similar a Victor Valdes
id_pl = df_player[df_player['player_name']=='Victor Valdes']['player_api_id'].values[0]
cluster = df_results[df_results['player_api_id'] == id_pl]['cluster'].values[0]
df_young[df_young['cluster'] == cluster].sort_values('overall_rating',
ascending=False)[['player_name','overall_rating']].head()
Com hem vist al punt 4.3.1 de l'anàlisi, les quatre principals lligues amb una mitjana superior de qualitat dels seus equips son l'anglesa, l'alemana, la italiana i la espanyola. En aquest mateix punt hem pogut observar, gràcies a l'anàlisi de Tukey, que la lliga espanyola és la més desigual, seguit de l'anglesa, entre el 1er quartil i la resta de quartils. Amb les següents gràfiques es pot observar visualment aquesta diferència.
fig, ax = plt.subplots(2,2,figsize=(15,10))
for num, country in enumerate(df_class_rat_4['league_name'].unique()):
df_league = df_class_rat_4[df_class_rat_4['league_name'] == country]
ax[num%2,num//2].set_title(country)
sns.distplot(df_league[df_league['quartile']==1]['weighed_rating'], hist=False, label='quartil 1',
ax=ax[num%2,num//2])
sns.distplot(df_league[df_league['quartile']==2]['weighed_rating'], hist=False, label='quartil 2',ax=ax[num%2,num//2])
sns.distplot(df_league[df_league['quartile']==3]['weighed_rating'], hist=False, label='quartil 3',ax=ax[num%2,num//2])
sns.distplot(df_league[df_league['quartile']==4]['weighed_rating'], hist=False, label='quartil 4',ax=ax[num%2,num//2])
ax[num%2,num//2].set_xlim(60,90)
En una primera visualització veiem la baixa correlació entre les dues variables per a totes les lligues.
def mod(X,slope, intercept):
return X * slope + intercept
slope, intercept, r_value, p_value, std_err = stats.linregress(df_classification_rating['weighed_rating'],
df_classification_rating['points_per_match'])
plt.figure(figsize=(7,7))
sns.scatterplot(x="weighed_rating", y="points_per_match",data=df_classification_rating,
hue='quartile')
plt.plot(range(55,90), mod(range(55,90),slope, intercept), 'k')
plt.text(85,.5,'R2 = {}'.format(round(r_value**2,2)))
plt.title('Totes les lligues')
En una segona representació d'aquesta correlació, hem agafat les 4 principals lligues que ja hem utilitzat anteriorment per veure la correlació per separat en cada lliga. Gràcies a les grafiques podem corroborar que la lliga espanyola es la que té la correlació més alta i es pot interpretar per la representació dels punts més junts.
fig, ax = plt.subplots(2,2,figsize=(15,10))
for num, country in enumerate(df_class_rat_4['league_name'].unique()):
df_league = df_class_rat_4[df_class_rat_4['league_name'] == country]
slope, intercept, r_value, p_value, std_err = stats.linregress(df_league['weighed_rating'],
df_league['points_per_match'])
sns.scatterplot(x="weighed_rating", y="points_per_match",data=df_league,
hue='quartile', ax=ax[num%2,num//2])
ax[num%2,num//2].plot(range(65,90), mod(range(65,90),slope, intercept), 'k')
ax[num%2,num//2].text(85,.5,'R2 = {}'.format(round(r_value**2,2)))
ax[num%2,num//2].set_title(country)
A l'anàlisi hem creat clusters per tipus de jugadors i hem extret el TOP5 de jugadors joves que estan al mateix cluster que jugadors com Leo Messi o Andrés Iniesta. A continuació podem veure visualment aquests clusters i on hem identificat alguns dels jugadors més coneguts per ajudar a entendre els perfils de jugadors que engloba els clusters.
# Per a poder visualitzar els grups, reduïm la dimensionalitar de les dades
X_tsne = pd.DataFrame(TSNE(2,learning_rate=200).fit_transform(df_player_attributes_standard),
columns=['x','y'])
df_results = df_results.reset_index().join(X_tsne)
plt.figure(figsize=(15,10))
sns.scatterplot(x='x', y='y', data=df_results, hue='cluster',size=1)
names=['Lionel Messi', 'Gerard Pique', 'Xavi Hernandez','Victor Valdes',
'Andres Iniesta','Iker Casillas','Cristiano Ronaldo','Sergio Busquets',
'Sergio Ramos','Carles Puyol','Luka Modric', 'Casemiro', 'Luis Suarez',
"Samuel Eto'o",'Jordi Alba', 'Sergio Aguero', 'Xabi Alonso',
'Daniel Carvajal','Daniel Alves','Ronaldinho','Neymar','Eden Hazard',
'Zlatan Ibrahimovic', 'Andrea Pirlo','Mesut Oezil', 'Deco',
'Cesc Fabregas','Raul', 'Kaka', 'Philipp Lahm', 'Manuel Neuer',
'Arturo Vidal', 'Paul Pogba','Arjen Robben', 'Rio Ferdinand',
'Raphael Varane', 'Pepe',
]
for name in names:
id_pl = df_player[df_player['player_name']==name]['player_api_id'].values[0]
plt.scatter(df_results[df_results['player_api_id'] == id_pl]['x'],
df_results[df_results['player_api_id'] == id_pl]['y'],
c='k', label='Nom Jugador')
plt.text(df_results[df_results['player_api_id'] == id_pl]['x'],
df_results[df_results['player_api_id'] == id_pl]['y'],
name, zorder=10)
plt.tight_layout()
A continuació podrem veure els atributs mitjans de cada perfil de jugador, que correspon a un cluster. Gràcies a un gràfic de radar podem comparar els diferetns perfils.
# Ens quedem amb els atributs que necessitem pel gràfic de radar
df_player_attributes_radar = df_player_attributes_imputed[['pac','sho','pas','dri','def','phy']]
# Reduïm les observacions a un sol registre per jugador i amb la mitjana dels seus atributs
df_player_attributes_radar = df_player_attributes_radar.reset_index().groupby('player_api_id').mean()
# Assignem a quin cluster pertany cada jugador
df_player_attributes_radar = df_player_attributes_radar.merge(
df_results[['player_api_id','cluster']],
on='player_api_id',
how='inner'
)
df_player_attributes_radar.head()
# Agrupem per cluster
df_cluster_mean = df_player_attributes_radar.reset_index().groupby('cluster').mean()
# Esborrem les columnes que no necessitem
del df_cluster_mean['index']
del df_cluster_mean['player_api_id']
df_cluster_mean
NOTA: Per a veure aquest gràfic obrir l'arxiu en format html en el mateix directori
categories = ['PAC','SHO','PAS','DRI','DEF','PHY']
fig = go.Figure()
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[0],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[0].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[1],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[1].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[2],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[2].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[3],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[3].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[4],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[4].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[5],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[5].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[6],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[6].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[7],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[7].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[8],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[8].name
))
fig.add_trace(go.Scatterpolar(
r=df_cluster_mean.iloc[9],
theta=categories,
fill='toself',
name=df_cluster_mean.iloc[9].name
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[20, 99]
)),
showlegend=True
)
fig.show()
#plotly.offline.plot(fig, filename = 'plot')
Per aquest exercici hem utilitzat un dataset molt complet que ens ha donat diferents possibilitats d'explotació de dades. En el nostre cas hem decidit analitzar les diferencias que hi han entre els equips que queden a la part alta o baixa de la classifiacació i si aquesta posició té relació amb la qualitat dels seus jugadors o es part de l'altazar del joc.
Hem pogut comprobar que aquesta hipotesi es correcte i que els equips que queden en posicions altes es degut a la qualitat dels seus futbolistes. L'explicació més evident és el pressupost que cada equip pot fer per fitxar millors jugadors. No obstant, per a les lligues alemans, anglesa i espanyola, no hem trobat diferències significatives entre el tercer i quart quartil de la classificació. Això vol dir que hi ha altres factors que influeixen en la classificació d'aquests equips. Alguns d'aquests poden no ser quantificables, com per exemple l'efecte que pot tenir en la moral dels jugadors un entrenador determinat o l'afició.
Donada aquesta les diferències entre equips grans i petits, hem fet l'estudi per trobar jugadors joves, que segurament siguin més accesibles per a equips de la part baixa de la classificació, però amb un perfil similar a jugadors considerats d'alt nivell. Hem pogut veure com jugadors com Raheem Sterling, considerats actualment com dels millors del món (Any 2020), ja ens ha sortit com a futura estrella en l'analisis de la temporada 2015/2016.
Tot el codi està present en aquest document Jupyter Notebook i es pot execitar seqüencialment pel seu funcionament.
| Contribucions | Firma |
|---|---|
| Investigació prèvia | CM, CG |
| Redacció de les respostes | CM, CG |
| Desenvolupament codi | CM, CG |